package xxl.ioc 
{
	import avmplus.DescribeTypeJSON;
	import flash.system.ApplicationDomain;
	import flash.utils.Dictionary;
	import flash.utils.getQualifiedClassName;
	import xxl.ioc.dependencyproviders.IDependencyProvider;
	import xxl.ioc.dependencyproviders.IFallbackDependencyProvider;
	import xxl.ioc.dependencyproviders.LocalOnlyProvider;
	import xxl.ioc.dependencyproviders.SoftDependencyProvider;
	import xxl.ioc.mapping.InjectionMapping;
	import xxl.ioc.reflection.DescribeTypeJSONReflector;
	import xxl.ioc.reflection.DescribeTypeReflector;
	import xxl.ioc.reflection.IReflector;
	import xxl.ioc.signals.MappingSignal;
	import xxl.ioc.typedescriptions.ConstructorInjectionPoint;
	import xxl.ioc.typedescriptions.InjectionPoint;
	import xxl.ioc.typedescriptions.PreDestroyInjectionPoint;
	import xxl.ioc.typedescriptions.TypeDescription;
	import xxl.ioc.utils.IOCInternal;
	import xxl.ioc.utils.TypeDescriptor;
	
	use namespace IOCInternal;
	/**
	 * ...
	 * @author XXL
	 */
	public class Injector 
	{
		
		private static const _BASE_TYPES:Array = _InitBaseTypeMappingIds([
			Object, Array, Class, Function, Boolean, Number, int, uint, String]);
		
		private static function _InitBaseTypeMappingIds(types:Array):Array
		{
			return types.map(function(type:Class, index:uint, list:Array):String
			{
				return getQualifiedClassName(type) + "|";
			});
		}
		
		private static var _INJECTION_POINTS_CACHE:Dictionary = new Dictionary(true);
		
		IOCInternal const providerMappings:Dictionary = new Dictionary();
		
		protected var _mappingSignal:MappingSignal = null;
		protected var _reflector:IReflector;
		
		private var _applicationDomain:ApplicationDomain;
		private var _parentInjector:Injector;
		private var _mappings:Dictionary;
		private var _mappingsInProcess:Dictionary;
		private var _managedObjects:Dictionary;
		private var _classDescriptor:TypeDescriptor;
		private var _fallbackProvider:IFallbackDependencyProvider;
		private var _blockParentFallbackProvider:Boolean = false;
		
		public function Injector() 
		{
			_mappings = new Dictionary();
			_mappingsInProcess = new Dictionary();
			_managedObjects = new Dictionary();
			try
			{
				_reflector = DescribeTypeJSON.available
					? new DescribeTypeJSONReflector()
					: new DescribeTypeReflector();
			}
			catch (e:Error)
			{
				_reflector = new DescribeTypeReflector();
			}
			_classDescriptor = new TypeDescriptor(_reflector, _INJECTION_POINTS_CACHE);
			_applicationDomain = ApplicationDomain.currentDomain;
		}
		
		public function get parentInjector():Injector
		{
			return _parentInjector;
		}
		
		public function set parentInjector(parentInjector:Injector):void
		{
			_parentInjector = parentInjector;
		}
		
		public function get applicationDomain():ApplicationDomain 
		{
			return _applicationDomain;
		}
		
		public function set applicationDomain(value:ApplicationDomain):void 
		{
			_applicationDomain = value;
		}
		
		public function get blockParentFallbackProvider():Boolean 
		{
			return _blockParentFallbackProvider;
		}
		
		public function set blockParentFallbackProvider(value:Boolean):void 
		{
			_blockParentFallbackProvider = value;
		}
		
		public function get fallbackProvider():IFallbackDependencyProvider 
		{
			return _fallbackProvider;
		}
		
		public function set fallbackProvider(value:IFallbackDependencyProvider):void 
		{
			_fallbackProvider = value;
		}
		
		public function get mappingSignal():MappingSignal
		{
			return _mappingSignal ||= new MappingSignal();
		}
		
		//----------public
		
		public function map(type:Class, name:String = ""):InjectionMapping
		{
			const mappingId:String = createMappingId(type, name);
			return _mappings[mappingId] || createMapping(type, name, mappingId);
		}
		
		public function unmap(type:Class, name:String = ""):void
		{
			const mappingId:String = createMappingId(type, name);
			const mapping:InjectionMapping = _mappings[mappingId];
			if (!mapping)
			{
				throw new Error("没有map");
			}
			if (mapping.isSealed)
			{
				throw new Error("不能unmap一个sealed的mapping");
			}
			mapping.getProvider().destroy();
			delete _mappings[mappingId];
			delete providerMappings[mappingId];
			//TODO:post_mapping_remove_event
		}
		
		public function satisfies(type:Class, name:String = ""):Boolean
		{
			const mappingId : String = createMappingId(type, name);
			return getProvider(mappingId, true) != null;
		}
		
		public function satisfiesDirectly(type:Class, name:String = ""):Boolean
		{
			const mappingId:String = createMappingId(type, name);
			return hasDirectMapping(type, name)
				|| getDefaultProvider(mappingId, false) != null;
		}
		
		public function hasMapping(type:Class, name:String = ""):Boolean
		{
			const mappingId:String = createMappingId(type, name);
			return getProvider(mappingId) != null;
		}
		
		public function hasDirectMapping(type:Class, name:String = ""):Boolean
		{
			const mappingId:String = createMappingId(type, name);
			return _mappings[mappingId] != null;
		}
		
		public function getMapping(type:Class, name:String = ""):InjectionMapping
		{
			const mappingId:String = createMappingId(type, name);
			const mapping:InjectionMapping = _mappings[mappingId];
			if (!mapping)
			{
				throw new Error("没有mapping");
			}
			return mapping;
		}
		
		public function injectInto(target:Object):void
		{
			const type:Class = _reflector.getClass(target);
			applyInjectionPoints(target, type, _classDescriptor.getDescription(type));
		}
		
		public function getInstance(type:Class, name:String = "", targetType:Class = null):*
		{
			const mappingId:String = createMappingId(type, name);
			const provider:IDependencyProvider = getProvider(mappingId) || getDefaultProvider(mappingId, true);
			if (provider)
			{
				const ctor:ConstructorInjectionPoint = _classDescriptor.getDescription(type).ctor;
				return provider.apply(targetType, this, ctor ? ctor.injectParameters : null);
			}
			throw new Error("没有mapping");
		}
		
		public function getOrCreateNewInstance(type:Class):*
		{
			return satisfies(type) && getInstance(type) || instantiateUnmapped(type);
		}
		
		public function instantiateUnmapped(type:Class):*
		{
			if (!canBeInstantiated(type))
			{
				throw new Error(type + "不能获得实例");
			}
			const description:TypeDescription = _classDescriptor.getDescription(type);
			const instance:* = description.ctor.createInstance(type, this);
			applyInjectionPoints(instance, type, description);
			return instance;
		}
		
		public function destroyInstance(instance:Object):void
		{
			if (!instance)
			{
				return;
			}
			const type:Class = _reflector.getClass(instance);
			const typeDescription:TypeDescription = getTypeDescription(type);
			for (var preDestoryHook:PreDestroyInjectionPoint = typeDescription.preDestoryMethods; preDestoryHook; preDestoryHook = PreDestroyInjectionPoint(preDestoryHook.next))
			{
				preDestoryHook.applyInjection(instance, type, this);
			}
		}
		
		public function getTypeDescription(type:Class):TypeDescription
		{
			return _classDescriptor.getDescription(type);
			//return _reflector.describeInjections(type);//原始方法存在不使用缓存直接使用新的描述的情况，不知道用意为何。
		}
		
		public function teardown():void
		{
			for each(var mapping:InjectionMapping in _mappings)
			{
				mapping.getProvider().destroy();
			}
			for each(var instance:Object in _managedObjects)
			{
				destroyInstance(instance);
			}
			_mappings = new Dictionary();
			_mappingsInProcess = new Dictionary();
			_managedObjects = new Dictionary();
			_fallbackProvider = null;
			_blockParentFallbackProvider = false;
		}
		
		public function createChildInjector(applicationDomain:ApplicationDomain = null):Injector
		{
			const injector:Injector = new Injector();
			injector.applicationDomain = applicationDomain || this.applicationDomain;
			injector.parentInjector = this;
			return injector;
		}
		
		public function addTypeDescription(type:Class, description:TypeDescription):void
		{
			_classDescriptor.addDescription(type, description);
		}
		
		//----------IOCInternal
		
		IOCInternal function getProvider(mappingId:String, fallbackToDefault:Boolean = true):IDependencyProvider
		{
			var softProvider:IDependencyProvider;
			var injector:Injector = this;
			while (injector)
			{
				var provider:IDependencyProvider = injector.IOCInternal::providerMappings[mappingId];
				if (provider)
				{
					if (provider is SoftDependencyProvider)
					{
						softProvider = provider;
						injector = injector.parentInjector;
						continue;
					}
					if (provider is LocalOnlyProvider && injector !== this)
					{
						injector = injector.parentInjector;
						continue;
					}
					return provider;
				}
				injector = injector.parentInjector;
			}
			if (softProvider)
			{
				return softProvider;
			}
			return fallbackToDefault?getDefaultProvider(mappingId, true):null;
		}
		
		IOCInternal function getDefaultProvider(mappingId:String, consultParents:Boolean):IDependencyProvider
		{
			if (_BASE_TYPES.indexOf(mappingId) > -1)
			{
				return null;
			}
			if (_fallbackProvider && _fallbackProvider.prepareNextRequest(mappingId))
			{
				return _fallbackProvider;
			}
			if (consultParents && !_blockParentFallbackProvider && _parentInjector)
			{
				return _parentInjector.getDefaultProvider(mappingId, consultParents);
			}
			return null;
		}
		
		IOCInternal function canBeInstantiated(type:Class):Boolean
		{
			const description:TypeDescription = _classDescriptor.getDescription(type);
			return description.ctor != null;
		}
		
		IOCInternal static function PurgeInjectionPointsCache():void
		{
			_INJECTION_POINTS_CACHE = new Dictionary(true);
		}
		
		//----------private/protected
		
		private function createMapping(type:Class, name:String, mappingId:String):InjectionMapping
		{
			if (_mappingsInProcess[mappingId])
			{
				throw new Error("正在创建" + mappingId);
			}
			_mappingsInProcess[mappingId] = true;
			//TODO:pre_mapping_create_event
			const mapping:InjectionMapping = new InjectionMapping(this, type, name, mappingId);
			_mappings[mappingId] = mapping;
			
			const sealKey:Object = mapping.seal();
			//TODO:post_mapping_create_event
			delete _mappingsInProcess[mappingId];
			mapping.unseal(sealKey);
			return mapping;
		}
		
		private function applyInjectionPoints(target:Object, targetType:Class, description:TypeDescription):void
		{
			var injectionPoint:InjectionPoint = description.injectionPoints;
			//TODO:pre_construct_event
			while (injectionPoint)
			{
				injectionPoint.applyInjection(target, targetType, this);
				injectionPoint = injectionPoint.next;
			}
			if (description.preDestoryMethods)
			{
				_managedObjects[target] = target;
			}
			//TODO:post_construct_event
		}
		
		//----------inline
		
		[Inline]
		final private function createMappingId(type:Class, name:String = ""):String
		{
			return getQualifiedClassName(type) + "|" + name;
		}
		
	}

}